//
//  aes_service.hpp
// ~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2013-2014 Razvan Coca (razvan dot coca at  gmail dot com)
// 
//


#ifndef AESEncryptionService_HPP
#define AESEncryptionService_HPP

#include <string>

#include <boost/function.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/bind/protect.hpp>

#include <boost/date_time.hpp>

#include <cryptopp/secblock.h>
#include <cryptopp/osrng.h>
#include <cryptopp/randpool.h>
#include <cryptopp/cryptlib.h>
#include <cryptopp/eax.h>
#include <cryptopp/aes.h>
#include "components/defs/util_types.hpp"
namespace components
{
namespace encrypt
{
class aes_service;
typedef boost::shared_ptr<aes_service> aes_service_pointer;

class aes_service: public boost::enable_shared_from_this<aes_service>,private boost::noncopyable
{

private:
    std::string name_;
    boost::asio::io_service& IoService;
    strand_pointer_t   Strand;
    CryptoPP::RandomPool RandPool;
    CryptoPP::EAX<CryptoPP::AES>::Encryption Encryptor;
    CryptoPP::EAX<CryptoPP::AES>::Decryption Decryptor;

    static boost::mutex create_mutex_;
    static int strand_index;
    static std::vector<strand_pointer_t> strands_;
    static byte  hw_threads;
    aes_service();
    explicit aes_service(boost::asio::io_service  & ios):
        IoService(ios),Strand(aes_service::get_strand(ios))
    {
        name_=std::string(__FUNCTION__);
    }
    static  strand_pointer_t get_strand(boost::asio::io_service& ios);

    template<typename Handler>
    void private_sencrypt(std::string const& src,std::string& dst, Handler& handler)
    {
        try
        {
            CryptoPP::StringSource(src,true,
                                   new CryptoPP::AuthenticatedEncryptionFilter(Encryptor,new CryptoPP::StringSink(dst))
                                   );
            handler(CryptoPP::Exception(CryptoPP::Exception::NOT_IMPLEMENTED,std::string("Success")));
        }
        catch(CryptoPP::Exception const &e )
        {
            handler(e);
        }
    }


    template<typename Handler>
    void private_sdecrypt(std::string const& src,std::string& dst,Handler& handler)
    {
        try
        {
            CryptoPP::StringSource(src,true,
                                   new CryptoPP::AuthenticatedDecryptionFilter(Decryptor,new CryptoPP::StringSink(dst))
                                   );
            handler(CryptoPP::Exception(CryptoPP::Exception::NOT_IMPLEMENTED,std::string("Success")));
        }
        catch(CryptoPP::Exception const &e )
        {
            handler(e);
        }
    }

public:

    enum direction {encrypt_op,decrypt_op};
    std::string const & name()
    {
        return name_;
    }
    static const int DEFAULT_KEYSIZE;
    static const int DEFAULT_BLOCKSIZE;

    virtual ~aes_service() {}

    static aes_service_pointer create(boost::asio::io_service  & ios)
    {
        return boost::shared_ptr<aes_service>(new aes_service(ios));
    };
    void enc_key(crypto_buf_t const& iv,crypto_buf_t const& key)
    {
        Encryptor.SetKeyWithIV(key.data(),key.size(),iv.data(),iv.size());
    }
    void dec_key(crypto_buf_t const& iv,crypto_buf_t const& key)
    {
        Decryptor.SetKeyWithIV(key.data(),key.size(),iv.data(),iv.size());
    }
    template<typename Handler>
    bool exec(direction dir,std::string const& src,std::string& dst,Handler& handler)
    {

        if(dir==encrypt_op)
        {
            auto func=boost::bind(&aes_service::private_sencrypt<Handler>,
                                  shared_from_this(),
                                  boost::cref(src),boost::ref(dst),
                                  handler);

            auto wrapped=Strand->wrap<decltype(func)>(func);
            IoService.post(wrapped);
            return true;
        }
        if(dir==decrypt_op)
        {
            auto func= boost::bind(&aes_service::private_sdecrypt<Handler>,shared_from_this(),
                                   boost::cref(src),boost::ref(dst),
                                   handler);

            auto wrapped=Strand->wrap<decltype(func)>(func);
            IoService.post(wrapped);
            return true;
        }
        return false;
    }


    bool cancel()
    {
        return false;
    }
};


} // end namespace encrypt
} // end namespace components

#endif // AESEncryptionService_HPP
